# 记录虚警框

import os
import operator
import math
import numpy as np
import cv2 as cv
from shapely.geometry import Polygon, MultiPoint  # 多边形


iou_thres = 0.4
out_file = 'overAlert_targets.txt'
if os.path.exists(out_file):
    os.remove(out_file)


def get_distance(p1, p2):
    return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)**0.5


def convert_xy4_to_xywha(xy4, IMGSIZE_W, IMGSIZE_H):
    p1 = [eval(xy4[0]), eval(xy4[1])]
    p2 = [eval(xy4[2]), eval(xy4[3])]
    p3 = [eval(xy4[4]), eval(xy4[5])]
    p4 = [eval(xy4[6]), eval(xy4[7])]

    # 四个顶点取均值 中心点
    # cx = (eval(xy4[0]) + eval(xy4[2]) + eval(xy4[4]) + eval(xy4[6])) / 4
    # cy = (eval(xy4[1]) + eval(xy4[3]) + eval(xy4[5]) + eval(xy4[7])) / 4
    cx = sum((p1[0], p2[0], p3[0], p4[0])) / 4
    cy = sum((p1[1], p2[1], p3[1], p4[1])) / 4

    # 顶点连线距离 排序 对角线 长 宽
    distances = list()  # six combinations
    distances.append(get_distance(p1, p2))
    distances.append(get_distance(p1, p3))
    distances.append(get_distance(p1, p4))
    distances.append(get_distance(p2, p3))
    distances.append(get_distance(p2, p4))
    distances.append(get_distance(p3, p4))
    distances.sort()  # catercorner * 2, longer_edge * 2, shorter_edge * 2

    w = (distances[2] + distances[3]) / 2  # longer edge
    h = (distances[0] + distances[1]) / 2  # shorter edge

    # 求旋转角度 角度制
    pp1, pp2, pp3, pp4 = sorted([p1, p2, p3, p4], key=operator.itemgetter(1))  # 四个顶点排序，以最低处的顶点pp1为起点
    # print(pp1 ,pp2, pp3, pp4)

    pp0 = pp2  # 设pp0为长边终点
    d = get_distance(pp1, pp0)
    temp = abs(d - w)  # d 与 w 的历史最小差距
    for ppi in [pp3, pp4]:
        d = get_distance(pp1, ppi)
        if abs(d - w) < temp:  # 若 基点到该点距离d 与 长边w 差距更小(更接近相等)
            temp = abs(d - w)  # 更新历史最小差距
            pp0 = ppi  # 切换终点
    # print(pp0)

    dy = pp0[1] - pp1[1]  # 必然>=0
    dx = pp0[0] - pp1[0]  # +-

    if dy < 1e-6:
        angle = 0  # the angle needs to be int degree
    elif abs(dx) < 1e-6:
        angle = 90  
    else:
        angle = int(math.atan(dy/dx) * 180 / math.pi)
    
    # 在0-179之间
    if angle < 0:
        angle += 180

    # 归一化
    cx = cx / IMGSIZE_W
    cy = cy / IMGSIZE_H
    w = w / IMGSIZE_W
    h = h / IMGSIZE_H

    return cx, cy, w, h, angle


def compute_one_rotation_iou(x_box, y_box):
    # 四边形的二维坐标表示
    xx_box, yy_box = np.array(x_box).reshape(4, 2), np.array(y_box).reshape(4, 2)
    # 构建四边形对象，会自动计算四个点的顺序：左上 左下  右下 右上 左上（返回5个点，最后回到起始点）
    x_poly, y_poly = Polygon(xx_box).convex_hull, Polygon(yy_box).convex_hull

    intersect_area = x_poly.intersection(y_poly).area  # 相交面积
    if intersect_area == 0:
        iou = 0
    else:
        union_area = x_poly.area + y_poly.area - intersect_area  # 总共面积
        iou = intersect_area / union_area
    return iou


def pred_lines2dict(lines_f):
    # 预测结果的字典
    result_dict = {}
    for lll in lines_f:
        lll = lll.split(' ')
        lll[0] = lll[0].split('/')[-1]
        lll[-1] = lll[-1][:-1]
        lll[-8:] = [eval(kk) for kk in lll[-8:]]

        if lll[0] not in result_dict:
            result_dict[lll[0]] = []
            result_dict[lll[0]].append(lll[-8:])
        else:
            result_dict[lll[0]].append(lll[-8:])
    return result_dict


def xywh2xy4(box_in, i_size):
    angle = int(box_in[5])
    cx = i_size * float(box_in[1])
    cy = i_size * float(box_in[2])
    w = i_size * float(box_in[3])
    h = i_size * float(box_in[4])
    arg = ((cx, cy), (w, h), angle)
    box = cv.boxPoints(arg)
    return np.int0(box)


if __name__ == '__main__':
    img_size = 3000
    gt_labels_path = 'D:\\GUANGBRIDGE\\OpticsData\\selected_train_val\\labels\\val\\'
    pred_result_path = 'D:\\GUANGBRIDGE\\OpticsData\\inferences\\inference-exp12-1000without_tiny+1000without_tiny\\results-conf0.1-best-BiCnnNew.txt'
    # 读入预测的结果， 统计预测框的总数量
    with open(pred_result_path, 'r') as fp:
        lines0 = fp.readlines()  # lines0: preds
    pred_nums = len(lines0)
    pred_dict = pred_lines2dict(lines0)
    # 统计预测准确的总数量
    pics_count = 0  # 所有GT中被检测出来的框的个数
    for pic in pred_dict:
        with open(gt_labels_path + pic[:-3] + 'txt', 'r') as fp:
            lines1 = fp.readlines()  # lines1: GTs

        for i in pred_dict[pic]:  # i: one pred box
            count = 0  # A flag representing whether this pred box is correct
            for line_ in lines1:
                line = line_.strip('\n').split(' ')
                line = [eval(kk) for kk in line]
                xy4 = xywh2xy4(line, img_size)  # xy4: one GT box

                iou_result = compute_one_rotation_iou(xy4, i)
                # print(iou_result)
                if iou_result >= iou_thres:
                    count = 1
                    break
            pics_count += count
            if count == 0:
                # print(i)
                (*xywha,) = convert_xy4_to_xywha([str(k) for k in i], img_size, img_size)
                cls0 = '0.0'
                log_line = ' '.join([pic, cls0, ' '.join([str(q) for q in xywha]), 'overAlert'])+'\n'
                with open(out_file, 'a') as fp:
                    fp.write(log_line)

    # 统计真实标签的总数量
    gt_nums = 0
    names_list = [name for name in os.listdir(gt_labels_path) if name.endswith('.txt')]
    for name in names_list:
        with open(gt_labels_path + name, 'r') as fp:
            lines0 = fp.readlines()
        count0 = len(lines0)
        gt_nums += count0
    print("正确预测的个数：", pics_count)
    print("总共预测的个数：", pred_nums)
    print("真实标签的个数：", gt_nums)
    print("检测率： {} %".format((pics_count/gt_nums) * 100))
    print("虚警率： {} %".format(((pred_nums-pics_count)/pred_nums) * 100))
